#include "highlighter.h"
#include "globalvar.h"

Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent)
{
    HighlightingRule rule;
    highlightingRules.clear();

    QTextCharFormat keywordFormat;
    keywordFormat.setForeground(GlobalVar::keywordColor[GlobalVar::colorTheme]);
    keywordFormat.setFontWeight(QFont::Bold);
    const QString keywordPatterns[] =
        {
            QStringLiteral("\\bchar\\b"), QStringLiteral("\\bclass\\b"), QStringLiteral("\\bconst\\b"),
            QStringLiteral("\\bdouble\\b"), QStringLiteral("\\benum\\b"), QStringLiteral("\\bexplicit\\b"),
            QStringLiteral("\\bfriend\\b"), QStringLiteral("\\binline\\b"), QStringLiteral("\\bint\\b"),
            QStringLiteral("\\blong\\b"), QStringLiteral("\\bnamespace\\b"), QStringLiteral("\\boperator\\b"),
            QStringLiteral("\\bprivate\\b"), QStringLiteral("\\bprotected\\b"), QStringLiteral("\\bpublic\\b"),
            QStringLiteral("\\bshort\\b"), QStringLiteral("\\bsignals\\b"), QStringLiteral("\\bsigned\\b"),
            QStringLiteral("\\bslots\\b"), QStringLiteral("\\bstatic\\b"), QStringLiteral("\\bstruct\\b"), QStringLiteral("\\bforeach\\b"),
            QStringLiteral("\\btemplate\\b"), QStringLiteral("\\btypedef\\b"), QStringLiteral("\\btypename\\b"), QStringLiteral("\\balignof\\b"),
            QStringLiteral("\\bunion\\b"), QStringLiteral("\\bunsigned\\b"), QStringLiteral("\\bvirtual\\b"), QStringLiteral("\\balignas\\b"),
            QStringLiteral("\\bvoid\\b"), QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bbool\\b"), QStringLiteral("\\bthread_local\\b"),
            QStringLiteral("\\bauto\\b"), QStringLiteral("\\btrue\\b"), QStringLiteral("\\bfalse\\b"), QStringLiteral("\\bdecltype\\b"),
            QStringLiteral("\\busing\\b"), QStringLiteral("\\bif\\b"), QStringLiteral("\\bgoto\\b"), QStringLiteral("\\bstatic_cast\\b"),
            QStringLiteral("\\belse\\b"), QStringLiteral("\\band\\b"), QStringLiteral("\\bconst_cast\\b"), QStringLiteral("\\bfor\\b"),
            QStringLiteral("\\bnot\\b"), QStringLiteral("\\bdynamic_cast\\b"), QStringLiteral("\\bnew\\b"), QStringLiteral("\\bwhile\\b"),
            QStringLiteral("\\bor\\b"), QStringLiteral("\\breinterpret_cast\\b"), QStringLiteral("\\bdelete\\b"), QStringLiteral("\\bdo\\b"),
            QStringLiteral("\\bxor\\b"), QStringLiteral("\\bstatic_assert\\b"), QStringLiteral("\\boverride\\b"), QStringLiteral("\\bthis\\b"),
            QStringLiteral("\\bswitch\\b"), QStringLiteral("\\breturn\\b"), QStringLiteral("\\bregister\\b"), QStringLiteral("\\bfloat\\b"),
            QStringLiteral("\\bwchar_t\\b"), QStringLiteral("\\bfinal\\b"), QStringLiteral("\\bnullptr\\b"), QStringLiteral("\\bcase\\b"),
            QStringLiteral("\\btry\\b"), QStringLiteral("\\bsizeof\\b"), QStringLiteral("\\bdefault\\b"), QStringLiteral("\\bcatch\\b"),
            QStringLiteral("\\bextern\\b"), QStringLiteral("\\btypeid\\b"), QStringLiteral("\\bbreak\\b"), QStringLiteral("\\bthrow\\b"),
            QStringLiteral("\\bcontinue\\b"), QStringLiteral("\\bnoexcept\\b"), QStringLiteral("\\bconstexpr\\b")
        };

    for (const QString &pattern : keywordPatterns) //关键字的匹配高亮
    {
        rule.pattern = QRegularExpression(pattern);
        rule.format = keywordFormat;
        highlightingRules.append(rule);
    }

    //#include等语句的高亮
    QTextCharFormat sharpFormat;
    sharpFormat.setForeground(GlobalVar::preprocessColor[GlobalVar::colorTheme]);
    rule.pattern = QRegularExpression(QStringLiteral("^[\\s]*#[^\n]*"));
    rule.format = sharpFormat;
    highlightingRules.append(rule);

    //以Q开头的Qt系统类的高亮
    QTextCharFormat classFormat;
    classFormat.setForeground(GlobalVar::qtClassColor[GlobalVar::colorTheme]);
    rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Zt][A-Za-z]*\\b"));
    rule.format = classFormat;
    highlightingRules.append(rule);

    //运算符，大中小括号，以及特殊符号的高亮
    operatorFormat.setFontWeight(QFont::Bold);
    operatorFormat.setForeground(GlobalVar::symbolColor[GlobalVar::colorTheme]);
    rule.pattern = QRegularExpression(QStringLiteral("[\\{\\}\\[\\]\\(\\)\\*\\-+/%^&|!~?=\\.,;:<>]"));
    rule.format = operatorFormat;
    highlightingRules.append(rule);

    //函数名的高亮
    QTextCharFormat functionFormat;
    functionFormat.setForeground(GlobalVar::functionColor[GlobalVar::colorTheme]);
    rule.pattern = QRegularExpression(QStringLiteral("\\b[\\w]+(?=\\()"));
    rule.format = functionFormat;
    highlightingRules.append(rule);

    //10进制数字的高亮
    QTextCharFormat numberFormat;
    numberFormat.setForeground(GlobalVar::numberColor[GlobalVar::colorTheme]);
    rule.pattern = numberExpression;
    rule.format = numberFormat;
    highlightingRules.append(rule);

    //16进制数字的高亮
    numberFormat.setForeground(GlobalVar::numberColor[GlobalVar::colorTheme]);
    rule.pattern = hexExpression;
    rule.format = numberFormat;
    highlightingRules.append(rule);

    //字符串显示颜色
    stringFormat.setForeground(GlobalVar::stringColor[GlobalVar::colorTheme]);

    //singleLineComment单行注释的字体设定
    singleLineCommentFormat.setForeground(GlobalVar::commentColor[GlobalVar::colorTheme]);
    singleLineCommentFormat.setFontItalic(true);

    //multiLineComment多行注释的字体
    multiLineCommentFormat.setForeground(GlobalVar::commentColor[GlobalVar::colorTheme]);
}

void Highlighter::highlightBlock(const QString &text)
{
    //关键字的高亮
    for (const HighlightingRule &rule : std::as_const(highlightingRules))
    {
        QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
        while (matchIterator.hasNext())
        {
            QRegularExpressionMatch match = matchIterator.next();
            setFormat(match.capturedStart(), match.capturedLength(), rule.format);
        }
    }

    //不等长字段的高亮，主要是字符，普通字符串，原生字符串，单行注释和多行注释的情况。
    setCurrentBlockState(0);//设定本段文本的状态为零
    QMap<int, QRegularExpression> tempMap; //int是开始索引，QRegularExpression是开始表达式
    QRegularExpression startExp; //字段开始正则表达式
    QRegularExpression endExp; //字段结束正则表达式
    int startIndex = 0; //所查询字段的开始位置，初始设置为0
    if (previousBlockState() <= 0) //如果前段落没有未结束的多行字段，开始新的查询
    {
        foreach (QRegularExpression exp, startExpList)
        {
            int index = text.indexOf(exp);
            if (index >= 0) //如果找到，则将匹配位置和表达式放入tempMap
                tempMap.insert(index, exp);
        }
        if (tempMap.size() > 0) //如果有匹配位置
        {
            startIndex = tempMap.begin().key();
            startExp = tempMap.begin().value();
            endExp = getEndExpFromStartExp(startExp);
        }
        else //如果没有匹配位置，开始位置设置为-1，结束本次高亮查询
            startIndex = -1;
    }
    else //如果前段落有尚未结束的多行字段，则选择多行字段的相应正则表达式
    {
        startExp = startExpList.at(previousBlockState() - 1);
        endExp = endExpList.at(previousBlockState() - 1);
    }

    while (startIndex >= 0)
    {
        QRegularExpressionMatch endMatch;

        int endIndex; //结束位置
        if (endExp == charEndExp) //如果是单个字符的情况，结束位置的检索需要特别设定。
        {
            if (previousBlockState() == 1 && startIndex == 0) endIndex = text.indexOf(charEndExp, 0, &endMatch);
            else endIndex = text.indexOf(charEndExp, startIndex + 1, &endMatch);
        }
        else if (endExp == stringEndExp) //如果是普通字符串的情况，结束位置的检索需要特别设定。
        {
            if (previousBlockState() == 2 && startIndex == 0) endIndex = text.indexOf(stringEndExp, 0, &endMatch);
            else endIndex = text.indexOf(stringEndExp, startIndex + 1, &endMatch);
        }
        else if (endExp == multiCommentEndExp) //如果是多行注释的情况，结束位置的检索需要特别设定。
        {
            if (previousBlockState() == 4 && startIndex == 0) endIndex = text.indexOf(multiCommentEndExp, 0, &endMatch);
            else endIndex = text.indexOf(multiCommentEndExp, startIndex + 2, &endMatch);
        }
        else //除了普通字符串之外的情况
            endIndex = text.indexOf(endExp, startIndex, &endMatch);

        int matchedLength; //匹配长度
        if (endIndex == -1) //如果没有发现结束标志，则从开始位置到字段尾进行高亮设定。
        {
            if (endExp == stringEndExp) //如果是普通字符串，则还需要段落尾是斜杠才能设定为多行字符串。
            {
                if (text.size() > 0 && text.last(1) == "\\")
                    setCurrentBlockState(2); //设定本段落是多行普通字符串
            }
            else if (endExp == rawStringEndExp || endExp == multiCommentEndExp)
            {
                setCurrentBlockState(endExpList.indexOf(endExp) + 1); //除了单个字符和普通字符串之外的情况，设定为相应多行字段。
            }

            matchedLength = text.length() - startIndex; //匹配长度
        }
        else //如果发现了结束标志，则设定匹配长度为以下公式
        {
            matchedLength = endIndex - startIndex + endMatch.capturedLength();
        }

        if (endExp == singleCommentEndExp) //如果是单行注释的情况
        {
            setCurrentBlockState(0); //设定为非多行字段
            matchedLength = text.length() - startIndex; //匹配长度是从startIndex到字段末尾
            setFormat(startIndex, matchedLength, singleLineCommentFormat);
            return;
        }

        if (endExp == multiCommentEndExp) //如果是多行注释，设定为多行注释的颜色
            setFormat(startIndex, matchedLength, multiLineCommentFormat);
        else //注释之外都是字符或者字符串，所以都设定为字符串颜色
            setFormat(startIndex, matchedLength, stringFormat);

        //寻找下一个开始位置
        if (endIndex == -1) //如果当前字段没有结束，则startIndex设为-1，结束循环。
            startIndex = -1;
        else //如果当前匹配字段结束，开始寻找下一个匹配字段
        {
            tempMap.clear(); //清空tempMap
            foreach (QRegularExpression exp, startExpList)
            {
                int index = text.indexOf(exp, startIndex + matchedLength);
                if (index >= 0)
                    tempMap.insert(index, exp);
            }
            if (tempMap.size() > 0)
            {
                startIndex = tempMap.begin().key();
                startExp = tempMap.begin().value();
                endExp = getEndExpFromStartExp(startExp);
            }
            else
                startIndex = -1;
        }
    }
}

QRegularExpression Highlighter::getEndExpFromStartExp(QRegularExpression startExp)
{
    QRegularExpression endExp;
    for (int i = 0; i < startExpList.size(); i++)
    {
        if (startExpList.at(i) == startExp)
        {
            endExp = endExpList.at(i);
            break;
        }
    }
    return endExp;
}
